
#ifndef RAY_H
#define RAY_H

#include <export.h>
#include <math/matrix.h>
#include <math/vector.h>
#include <float.h>

/**
 * \addtogroup math
 * @{
 */

/**
 * @brief Matematical representation of a ray
 */
class CE_API ceRay
{
private:
  ceVector3f _origin;
  ceVector3f _direction;

public:
  inline ceRay() : _origin (0, 0, 0), _direction (0, 0, 1) { }
  inline ceRay (const ceVector3f& direction) : _origin (0, 0, 0), _direction (direction) { }
  inline ceRay (const ceVector3f& origin, const ceVector3f& direction) : _origin (origin), _direction (direction) { }

  inline void SetOrigin (const ceVector3f& origin) { _origin = origin; }
  inline const ceVector3f& GetOrigin () const { return _origin; }
  inline ceVector3f& GetOrigin () { return _origin; }

  inline void SetDirection (const ceVector3f& direction) { _direction = direction; }
  inline const ceVector3f& GetDirection () const { return _direction; }
  inline ceVector3f& GetDirection () { return _direction; }

  inline float GetLocation (const ceVector3f& point) const
  {
    return _direction.Dot(point - _origin);
  }

  inline ceVector3f GetLocation (float pos) const
  {
    return _origin + _direction * pos;
  }

  inline ceVector3f GetAbsLocationX (float x) const
  {
    float alpha = (x - _origin.x) / _direction.x;
    return GetLocation(alpha);
  }

  inline ceVector3f GetAbsLocationY (float y) const
  {
    float alpha = (y - _origin.y) / _direction.y;
    return GetLocation(alpha);
  }

  inline ceVector3f GetAbsLocationZ (float z) const
  {
    float alpha = (z - _origin.z) / _direction.z;
    return GetLocation(alpha);
  }

  ceVector3f GetLocation (const ceRay& other)
  {
    float d00 = _direction.Dot();
    float d01 = _direction.Dot(other._direction);
    float d10 = d01;
    float d11 = other._direction.Dot();

    ceMatrix2f M (-d00, d10,
                  d01, -d11);
    M.Invert();

    ceVector2f v ((_origin - other._origin).Dot(_direction),
                  (other._origin - _origin).Dot(other._direction));


    ceVector2f a = M * v;

    return GetLocation(a.x);
  }

  static float GetDistance (const ceRay& r0, const ceRay& r1, float *limitR0 = 0, float *limitR1 = 0)
  {
    float d00 = r0._direction.Dot();
    float d01 = r0._direction.Dot(r1._direction);
    float d10 = d01;
    float d11 = r1._direction.Dot();

    ceMatrix2f M (-d00, d10,
                  d01, -d11);
    M.Invert();

    ceVector2f v ((r0._origin - r1._origin).Dot(r0._direction),
                  (r1._origin - r0._origin).Dot(r1._direction));


    ceVector2f a = M * v;

    ceVector3f pp0 = r0.GetLocation(a.x);
    ceVector3f pp1 = r1.GetLocation(a.y);

    ceVector3f p0 = pp0;
    ceVector3f p1 = pp1;

    bool out0 = false;
    if (limitR0)
      {
        if (a.x < 0.0f)
          {
            p0 = r0.GetLocation(0.0f);
            out0 = true;
          }
        else if (a.x > *limitR0)
          {
            p0 = r0.GetLocation(*limitR0);
            out0 = true;
          }
      }

    float out1 = false;
    if (limitR1)
      {
        if (a.y < 0.0f)
          {
            p1 = r1.GetLocation(0.0f);
            out1 = true;
          }
        else if (a.y > *limitR1)
          {
            p1 = r0.GetLocation(*limitR1);
            out1 = true;
          }
      }

    if (out0 && out1)
      {
        float d0 = (pp0 - p1).Length();
        float d1 = (p0 - pp1).Length();
        if (d0 < d1)
          {
            return d0;
          }
        return d1;
      }

    return (p0 - p1).Length();

  }

  friend inline ceRay operator* (const ceMatrix4f& m, const ceRay& r)
  {
    ceVector4f o (r.GetOrigin(), 1.0f);
    ceVector4f d (r.GetDirection(), 0.0f);

    o = m * o;
    d = m * d;

    return ceRay (o.AsVector3(), d.AsVector3());
  }

  inline void Debug (const char* msg = 0) const
  {
    if (msg) printf ("Ray: %s", msg);
    else printf ("Ray");
    printf ("  %.2f %.2f %.2f => %.2f %.2f %.2f\n",
            _origin.x, _origin.y, _origin.z,
            _direction.x, _direction.y, _direction.z);
  }

};

/** @} */


#endif // RAY_H
